package common;

import ap.PositionTuple;


import java.io.*;
import java.util.*;

public class Network {
    static final String ipPrefix = "127.0.0.";
    static final int socketPortStart = 10000;
    int ipEnd;

    HashMap<PositionTuple, PositionTuple> topology;
    HashMap<String, HashSet<PositionTuple>> devicePort;
    HashMap<String, String> ipMap; // deviceName -> deviceIp
    HashMap<String, HashMap<String, Integer>> socketPortMap;
    HashMap<String, ArrayList<String>> fibList;
    ArrayList<Req> reqList;

    public static void main(String[] args) {
        Network n = new Network();
        n.addTopology("S", "A", "A", "S");
        n.addTopology("A", "B", "B", "A");
        n.addTopology("B", "E", "E", "B");
        n.addTopology("B", "F", "F", "B");
        n.addTopology("B", "C", "C", "B");
        n.addTopology("C", "D", "D", "C");
        n.addTopology("C", "E", "E", "C");
        n.addTopology("C", "F", "F", "C");
        n.addTopology("E", "F", "F", "E");
        n.addTopology("E", "G", "G", "E");
        n.addTopology("F", "K", "K", "F");
        n.addTopology("K", "L", "L", "K");
        n.addTopology("G", "H", "H", "G");
        n.addTopology("H", "I", "I", "H");
        n.addTopology("L", "J", "J", "L");
        n.addTopology("D", "J", "J", "D");
        n.addTopology("D", "I", "I", "D");
        n.addTopology("J", "I", "I", "J");
        n.addTopology("B", "W", "W", "B");
        n.addTopology("F", "W", "W", "F");
        n.addTopology("C", "W", "W", "C");
        n.addTopology("E", "W", "W", "E");


        HashSet<String> start = new HashSet<>(), end = new HashSet<>(), waypoint = new HashSet<>();
        start.add("S");
        waypoint.add("W");
        end.add("D");
        n.genDVNet(start, end, waypoint, null);

    }

    public Network(){
        topology = new HashMap<>();
        devicePort = new HashMap<>();
        ipMap = new HashMap<>();
        ipEnd = 2;
        socketPortMap = new HashMap<>();
        fibList = new HashMap<>();
        reqList = new ArrayList<>();
    }

    public void addTopology(String d1, String p1, String d2, String p2) {
        PositionTuple pt1 = new PositionTuple(d1, p1);
        PositionTuple pt2 = new PositionTuple(d2, p2);

        if(!ipMap.containsKey(d1)) ipMap.put(d1, String.format("%s%d", ipPrefix, ipEnd++));
        if(!ipMap.containsKey(d2)) ipMap.put(d2, String.format("%s%d", ipPrefix, ipEnd++));

        if(!socketPortMap.containsKey(d1)) socketPortMap.put(d1, new HashMap<>());
        HashMap<String, Integer> _t = socketPortMap.get(d1);
        if(!_t.containsKey(p1)) _t.put(p1, socketPortStart + _t.size());


        if(!socketPortMap.containsKey(d2)) socketPortMap.put(d2, new HashMap<>());
        _t = socketPortMap.get(d2);
        if(!_t.containsKey(p2)) _t.put(p2, socketPortStart + _t.size());

        // links are two way
        if(topology.containsKey(pt1))
        {
            // 为了方便生成图，暂时改成一对一
            System.err.printf("{%s:%s}重复连接多个端口\n", pt1.getDeviceName(), pt1.getPortName());
            // topology.get(pt1).add(pt2);
        }else
        {
            topology.put(pt1, pt2);
        }
        if(topology.containsKey(pt2))
        {
            System.err.printf("{%s:%s}重复连接多个端口\n", pt2.getDeviceName(), pt2.getPortName());
            //topology.get(pt2).add(pt1);
        }else
        {
            topology.put(pt2, pt1);
        }

        if(!devicePort.containsKey(d1)) devicePort.put(d1, new HashSet<>());
        if(!devicePort.containsKey(d2)) devicePort.put(d2, new HashSet<>());
        devicePort.get(d1).add(pt1);
        devicePort.get(d2).add(pt2);
    }

    public void addReqByPorts(String req, HashSet<PositionTuple> startPorts, HashSet<PositionTuple> endPorts, HashSet<String> waypoint,
                       HashSet<Integer> lengthSet){
        Req r = new Req(req, this);
        r.setRequirementsByPorts(startPorts, endPorts, waypoint, lengthSet);
        this.reqList.add(r);
    }

    public void addReqByNodes(String req, HashSet<String> startPorts, HashSet<String> endPorts, HashSet<String> waypoint,
                              HashSet<Integer> lengthSet){
        Req r = new Req(req, this);
        r.setRequirements(startPorts, endPorts, waypoint, lengthSet);
        this.reqList.add(r);
    }

    public void addReq(String req, HashSet<String> startNodes, HashSet<String> endNodes, HashSet<String> waypoint,
                       HashSet<Integer> lengthSet){
        Req r = new Req(req, this);
        r.setRequirements(startNodes, endNodes, waypoint, lengthSet);
        this.reqList.add(r);
    }

    public void addFib(String device, String ip, int prefixLen, String action){
        if(!fibList.containsKey(device)) fibList.put(device, new ArrayList<>());
        fibList.get(device).add(String.format("fwd %s %s %s", Utility.ip2Int(ip), prefixLen, action));
    }

    public CompactPG genDVNet(HashSet<String> startNode, HashSet<String> endNode, HashSet<String> waypoint, HashSet<Integer> lengthSet){
        ProductGraph pg = new ProductGraph(this);
        pg.setRequirement(startNode, endNode, waypoint, lengthSet);
        return pg.genDVNet();
    }

    public void generateConfig(String path){
        File pa = new File(path);
        pa.mkdirs();
        File rf = new File(path+"/requirement.txt");
        try {
            FileWriter rfw = new FileWriter(rf);
            for(Req req:reqList){
                rfw.write("0 0 0 " + req.req+"\n");
            }
            rfw.flush();
            rfw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }



        for(String device:devicePort.keySet()){
            File file = new File(path+"/" + device);
            try {
                FileWriter fw = new FileWriter(file);
                fw.write(String.format("%s %s\n", device, ipMap.get(device)));

                for(PositionTuple pt:topology.keySet()){
                    if(!pt.getDeviceName().equals(device)) continue;
                    PositionTuple to = topology.get(pt);
                    String line = String.format("link %s %s %s %s\n", pt.getPortName(), ipMap.get(to.getDeviceName()),
                            socketPortMap.get(pt.getDeviceName()).get(pt.getPortName()),
                            socketPortMap.get(to.getDeviceName()).get(to.getPortName()));
                    fw.write(line);
                }
                int i=0;
                for(Req req:reqList){
                    ArrayList<Integer> list = req.cpg.bucket.get(device);
                    int nodesNum = list==null?0:list.size();
                    fw.write(String.format("req %s %s\n", i, nodesNum));
                    if(list != null)
                        for(Integer idx:list){
                            CompactNode node = req.cpg.nodes.get(idx);
                            for(Pair n:node.next)
                                fw.write(String.format("flag %s %s next %s %s\n", i, node.index, n.Port, n.Index));
                            for(Pair n:node.prev)
                                fw.write(String.format("flag %s %s prev %s %s\n", i, node.index, n.Port, n.Index));
                        }
                    i++;
                }
                if(fibList.containsKey(device)) {
                    for(String fib:fibList.get(device)){
                        fw.write(fib+"\n");
                    }
                }
                fw.flush();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
                System.err.printf("生成配置文件%s失败\n", device);
                System.exit(0);
            }
        }
    }
}

class Req{
    String req;
    ProductGraph pg;
    CompactPG cpg;

    HashSet<String> startNodes, endNodes, waypoints;
    HashSet<Integer> lengthSet;

    public Req(String req, Network nt){
        this.req = req;
        pg = new ProductGraph(nt);
    }

    public void setRequirements(HashSet<String> startNodes, HashSet<String> endNodes, HashSet<String> waypoint, HashSet<Integer> lengthSet){
        pg.setRequirement(startNodes, endNodes, waypoint, lengthSet);
        cpg = pg.genDVNet();
    }

    public void setRequirementsByPorts(HashSet<PositionTuple> startPorts, HashSet<PositionTuple> endPorts, HashSet<String> waypoint, HashSet<Integer> lengthSet){
        pg.setRequirementByPort(startPorts, endPorts, waypoint, lengthSet);
        cpg = pg.genDVNet();
    }


}

class ProductGraph{
    Network network;
    ArrayList<ReqDFA> reqsList;
    HashSet<String> startNodes, endNodes, waypoints;
    HashSet<PositionTuple> startPorts, endPorts;
    HashSet<Integer> lengthSet;
    int minLength, maxLength;
    ArrayList<VNode> nodes;
    HashMap<String, Integer> nodesMap;
    CompactPG pg;

    public ProductGraph(Network n){
        network = n;
        nodes = new ArrayList<>();
        nodesMap = new HashMap<>();
        reqsList = new ArrayList<>();

        startNodes = new HashSet<>();
        startPorts = new HashSet<>();
        endPorts = new HashSet<>();
        endNodes = new HashSet<>();
        waypoints = new HashSet<>();
        lengthSet = new HashSet<>();
    }

    public void setRequirement(HashSet<String> startNodes, HashSet<String> endNodes, HashSet<String> waypoints,
                               HashSet<Integer> lengthSet) {
        this.startNodes = startNodes;
        this.endNodes = endNodes;

        for(String node:startNodes)
            this.startPorts.addAll(network.devicePort.get(node));

        for(String node:endNodes)
            this.endPorts.addAll(network.devicePort.get(node));

        if(waypoints != null) this.waypoints = waypoints;
        if(lengthSet != null) this.lengthSet = lengthSet;
    }

    public void setRequirementByPort(HashSet<PositionTuple> startPorts, HashSet<PositionTuple> endPorts, HashSet<String> waypoints,
                               HashSet<Integer> lengthSet) {
        this.startPorts = startPorts;
        this.endPorts = endPorts;

        for(PositionTuple p:startPorts)
            this.startNodes.add(p.getDeviceName());
        for(PositionTuple p:endPorts)
            this.endNodes.add(p.getDeviceName());

        if(waypoints != null) this.waypoints = waypoints;
        if(lengthSet != null) this.lengthSet = lengthSet;
    }

    public void setEndNodes(String ... nodes){
        this.endNodes = new HashSet<>();
        this.endNodes.addAll(Arrays.asList(nodes));
    }

    public void setWaypoints(String ... nodes){
        this.waypoints = new HashSet<>();
        this.waypoints.addAll(Arrays.asList(nodes));
    }

    public void setAcceptLength(Integer ... lengthList){
        this.lengthSet = new HashSet<>();
        this.lengthSet.addAll(Arrays.asList(lengthList));
    }
    // 生成状态图
    public void genStateMap(){
        reqsList.clear();

        // 1、 生成经过所有waypoint并到达end 的状态机图
        int startState = 0, endState = 1 + waypoints.size();
        // 当前状态state -> (输出的 端口 -> 下一个状态state)
        HashMap<Integer, HashMap<String, Integer>> w = new HashMap<>();

        for(int i=startState; i<endState;i++){
            // 输出的 端口 -> 下一个状态state
            HashMap<String, Integer> table = new HashMap<>();
            for(PositionTuple p:network.topology.keySet()){
                PositionTuple to = network.topology.get(p);
                if(i != endState-1 && waypoints.contains(to.getDeviceName()) ||
                        i == endState-1 && endPorts.contains(to)){
                    table.put(portToKey(p), i+1);
                }else{
                    table.put(portToKey(p), i);
                }
                if(i == startState) table.put(p.getDeviceName(), i);  // init
          }
            w.put(i, table);
        }
        reqsList.add(new ReqDFA("waypoint", 0, w, new int[]{endState}));

        // 循环检测
        HashMap<Integer, HashMap<String, Integer>> loopMap;
        for(String dectDevice: network.devicePort.keySet()){
            loopMap = new HashMap<>();
            HashMap<String, Integer>loop0 = new HashMap<>();
            HashMap<String, Integer>loop1 = new HashMap<>();

            for(PositionTuple p:network.topology.keySet()){
                if(network.topology.get(p).getDeviceName().equals(dectDevice)){
                    loop0.put(portToKey(p), 1);
                }else{
                    loop0.put(portToKey(p), 0);
                    loop1.put(portToKey(p), 1);
                }
                loop0.put(p.getDeviceName(), p.getDeviceName().equals(dectDevice)?1:0);
            }

            loopMap.put(0, loop0);
            loopMap.put(1, loop1);
            reqsList.add(new ReqDFA("Loop-"+dectDevice, 0, loopMap, new int[] {0, 1}));
        }

        // set length
        if(lengthSet.size() > 0){
            int max = Collections.max(lengthSet);
            HashMap<Integer, HashMap<String, Integer>> table = new HashMap<>();
            for(int i=0; i <= max;i++){
                HashMap<String, Integer> cur = new HashMap<>();
                for(PositionTuple p:network.topology.keySet()){
                    cur.put(portToKey(p), i+1);
                    if(i==0) cur.put(p.getDeviceName(), i);
                }
                table.put(i, cur);
            }

            reqsList.add(new ReqDFA("length", 0, table, lengthSet));
        }
    }

    public int addNode(String pNodeName, int[] states, boolean accept){
        nodes.add(new VNode(pNodeName, states, accept));
        nodesMap.put(toKey(pNodeName, states), nodes.size()-1);
        return nodes.size()-1;
    }

    public void addEdge(int src, PositionTuple srcPort, int dst){
        nodes.get(src).next.put(srcPort, dst);
        nodes.get(dst).prev.put(network.topology.get(srcPort), src);
    }

    public static String toKey(String pNodeName, int[] states){
        return String.format("%s,%s", pNodeName, Arrays.toString(states));
    }

    public static  String portToKey(PositionTuple p){
        return String.format("%s-%s", p.getDeviceName(), p.getPortName());
    }

    private boolean isAccept(int[] states){
        for(int i=0;i<states.length;i++){
            if(!reqsList.get(i).acceptStates.contains(states[i]))
                return false;
        }
        return true;
    }

    private Integer checkExist(String pNodeName, int[] states){
        return nodesMap.get(toKey(pNodeName, states));
    }

    private void addToBucket(String key, int val, HashMap<String, ArrayList<Integer>> bucket){
        if(!bucket.containsKey(key))
            bucket.put(key, new ArrayList<>());
        bucket.get(key).add(val);
    }

    public CompactPG minimize(){
        boolean canMerge = true;
        int n = nodes.size();
        HashMap<String, ArrayList<Integer>> bucket = new HashMap<>();
        for(String s: network.devicePort.keySet()){
            bucket.put(s, new ArrayList<>());
        }
        Queue<Integer> que = new LinkedList<>();
        boolean[] nodesValid = new boolean[n];
        for(int i=0;i<n;i++){

            if(nodes.get(i).accept){
                que.add(i);
                nodesValid[i] = true;
                addToBucket(nodes.get(i).pNode, i, bucket);
            }
        }

        while (!que.isEmpty()){
            int cur = que.remove();
            for(Integer pred: nodes.get(cur).prev.values()){
                if(!nodesValid[pred]){
                    que.add(pred);
                    addToBucket(nodes.get(pred).pNode, pred, bucket);
                    nodesValid[pred] = true;
                }
            }
        }

        for(int i=0;i<n;i++) {
            if (!nodesValid[i]) {
                VNode vNode = nodes.get(i);
                for(PositionTuple pt: vNode.prev.keySet()){
                    int predIdx = vNode.prev.get(pt);
                    nodes.get(predIdx).next.remove(network.topology.get(pt));
                }
                for(PositionTuple pt: nodes.get(i).next.keySet()){
                    int nextIdx = vNode.next.get(pt);
                    nodes.get(nextIdx).prev.remove(network.topology.get(pt));
                }
            }
        }

        boolean flag = true;
        while (flag){
            flag = false;
            for(String pName:bucket.keySet()){
                canMerge = false;
                HashSet<Integer> mergeTarget = new HashSet<>();
                ArrayList<Integer> nodeList = bucket.get(pName);
                n = nodeList.size();
                int i;
                for(i=0;i<n-1;i++){
                    for(int j=i+1;j<n;j++){
                        if(nodes.get(nodeList.get(i)).canMerge(nodes.get(nodeList.get(j)))){
                            mergeTarget.add(j);
                            canMerge = true;
                        }
                    }
                    if(canMerge) break;
                }
                if(canMerge){
                    flag = true;
                    for(Integer m:mergeTarget){
                        nodesValid[nodeList.get(m)] = false;
                        VNode node = nodes.get(nodeList.get(m));
                        for(PositionTuple pt:node.prev.keySet()){
                            int idx = node.prev.get(pt);
                            nodes.get(idx).next.put(network.topology.get(pt), nodeList.get(i));
                            nodes.get(nodeList.get(i)).prev.put(pt, idx);
                        }
                    }
                    ArrayList<Integer> nl = new ArrayList<>();
                    for(int j=0;j<nodeList.size();j++){
                        if(!mergeTarget.contains(j)) nl.add(nodeList.get(j));
                    }
                    bucket.put(pName, nl);
                }
            }
        }

        CompactPG pg = new CompactPG();
        HashMap<Integer, Integer> idxMap = new HashMap<>();
        for(int i=0;i<nodes.size();i++) {
            if (nodesValid[i]){
                idxMap.put(i, pg.addNode(nodes.get(i).pNode));
            }
        }
        for(int i=0;i<nodes.size();i++) {
            if (nodesValid[i]){
                VNode node = nodes.get(i);
                for(PositionTuple pt:node.next.keySet()){
                    int dst = node.next.get(pt);
                    pg.addEdge(idxMap.get(i), pt, idxMap.get(dst), network.topology.get(pt));
                }
            }
        }

        System.out.println("finish minimize.Result:");
        for(CompactNode node: pg.nodes){
            System.out.printf("%s%s, next:{%s} prev:{%s}\n",
                    node.pNodeName, node.index, node.next, node.prev);
        }
        dfs(new Stack<>(), 0, idxMap, nodesValid,pg, new HashSet<>());
        return pg;
    }

    public void dfs(Stack<String> s, int nodeIndex, HashMap<Integer, Integer> idxMap, boolean[] nodesValid, CompactPG pg, HashSet<String> res){
        if (!nodesValid[nodeIndex]){return;}
        VNode node = nodes.get(nodeIndex);
        int index = pg.nodes.get(idxMap.get(nodeIndex)).index;
        s.add(("\"" + node.pNode+"-" + index+ "\""));
        if(node.next.size() == 0){
            List<String> l = new ArrayList<>(s);
            if(!res.contains(l.toString())){
                System.out.println(l + ",");
                res.add(l.toString());
            }
            s.pop();
            return;
        }
        for(PositionTuple pt:node.next.keySet()){
            int dst = node.next.get(pt);
            dfs(s, dst, idxMap, nodesValid, pg, res);
        }
        s.pop();
    }

    public CompactPG genDVNet(){
        Queue<Integer> nodeQueue = new LinkedList<>();
        genStateMap();
        HashMap<String, Integer> sn = new HashMap<>();
        for(String node: startNodes){
            int[] state = new int[reqsList.size()];
            for(int i=0;i<reqsList.size();i++){
                ReqDFA rd = reqsList.get(i);
                state[i] = rd.getInitState(node, rd.initState);
            }
            sn.put(node, addNode(node, state, isAccept(state)));
        }

        for(PositionTuple p:startPorts){
            int cur = sn.get(p.getDeviceName());
            VNode node = nodes.get(cur);
            int[] states = new int[reqsList.size()];
            boolean flag = true;
            for(int i=0;i<reqsList.size();i++){
                Integer state = reqsList.get(i).getNextState(p, node.states[i]);
                if(state == null){
                    flag = false;
                    break;
                }
                states[i] = state;
            }
            if(flag){
                Integer temp = checkExist(network.topology.get(p).getDeviceName(), states);
                if(temp == null){
                    int t = addNode(network.topology.get(p).getDeviceName(), states, isAccept(states));
                    addEdge(cur, p, t);
                    nodeQueue.add(t);
                }else{
                    addEdge(cur, p, temp);
                }
            }
        }


        while(!nodeQueue.isEmpty()){
            int cur = nodeQueue.remove();
            VNode node = nodes.get(cur);
            if(node.accept) continue; // 当前满足所有条件则不继续遍历
            HashSet<PositionTuple> ps = network.devicePort.get(node.pNode);
            for(PositionTuple p:ps){
                int[] states = new int[reqsList.size()];
                boolean flag = true;
                for(int i=0;i<reqsList.size();i++){
                    Integer state = reqsList.get(i).getNextState(p, node.states[i]);
                    if(state == null){
                        flag = false;
                        break;
                    }
                    states[i] = state;
                }
                if(flag){
                    Integer temp = checkExist(network.topology.get(p).getDeviceName(), states);
                    if(temp == null){
                        int t = addNode(network.topology.get(p).getDeviceName(), states, isAccept(states));
                        addEdge(cur, p, t);
                        nodeQueue.add(t);
                    }else{
                        addEdge(cur, p, temp);
                    }
                }
            }
        }
        System.out.println(nodes.size());
        int i=0;
        for(VNode vNode: nodes){
            System.out.printf("%s-%s accept:{%s} next:{%s} prev:{%s}\n",
                    i++, vNode.pNode, vNode.accept, vNode.next, vNode.prev);
        }
        return minimize();
    }

    public String printState(int[] states){
        assert states.length == reqsList.size();
        ArrayList<Pair> pl = new ArrayList<>();
        for(int i=0;i<states.length;i++){
            pl.add(new Pair(reqsList.get(i).name, states[i]));
        }
        return pl.toString();
    }


}

class VNode{
    String pNode;
    int[] states;
    boolean accept;
    HashMap<PositionTuple, Integer> next;
    HashMap<PositionTuple, Integer> prev;

    public VNode(String pNode, int[] states, boolean accept){
        this.pNode = pNode;
        this.states = states;
        this.accept = accept;
        next = new HashMap<>();
        prev = new HashMap<>();
    }

    public boolean canMerge(VNode o){
        if(!o.pNode.equals(pNode)) return false;
        if(this.next.size() !=  o.next.size()) return false;
        for(PositionTuple pt:o.next.keySet()){
            if(!next.containsKey(pt)) return false;
        }
        return true;
    }
}

class ReqDFA{
    String name;
    int initState;
    HashSet<Integer> acceptStates;
    HashMap<Integer, HashMap<String, Integer>> stateTable;

    public ReqDFA(String name, int initState, HashMap<Integer, HashMap<String, Integer>> stateTable, int[] accept){
        this.name = name;
        this.initState = initState;
        this.stateTable = stateTable;
        this.acceptStates = new HashSet<>();
        for(Integer i: accept)
            acceptStates.add(i);
    }
    public ReqDFA(String name, int initState, HashMap<Integer, HashMap<String, Integer>> stateTable,
                  HashSet<Integer> accept){
        this.name = name;
        this.initState = initState;
        this.stateTable = stateTable;
        this.acceptStates = accept;
    }
    public Integer getNextState(PositionTuple p, int state){
        HashMap<String, Integer> s = stateTable.get(state);
        if(s != null) {
            if(s.containsKey(ProductGraph.portToKey(p)))
                return s.get(ProductGraph.portToKey(p));
        }
        return null;
    }

    public Integer getInitState(String device, int state){
        HashMap<String, Integer> s = stateTable.get(state);
        if(s != null) {
            if(s.containsKey(device))
                return s.get(device);
        }
        return null;
    }
}

class CompactNode{
    String pNodeName;
    int index;
    ArrayList<Pair> next;
    ArrayList<Pair> prev;
//    HashMap<String, Integer> prev;
//    HashMap<String, Integer> next;

    public CompactNode(String pNodeName, int index){
        this.pNodeName = pNodeName;
        this.index = index;
        next = new ArrayList<>();
        prev = new ArrayList<>();
    }
}

class CompactPG{
    ArrayList<CompactNode> nodes;
    HashMap<String, ArrayList<Integer>> bucket;

    public CompactPG(){
        nodes = new ArrayList<>();
        bucket = new HashMap<>();
    }

    public int addNode(String pNodeName){
        int tag = 0;
        if(!bucket.containsKey(pNodeName)){
            bucket.put(pNodeName, new ArrayList<>());
        }else{
            tag = bucket.get(pNodeName).size();
        }
        nodes.add(new CompactNode(pNodeName, tag));
        int idx = nodes.size()-1;
        bucket.get(pNodeName).add(idx);
        return idx;
    }

    public void addEdge(int src, PositionTuple srcPort, int dst, PositionTuple dstPort){
        nodes.get(src).next.add(new Pair(srcPort.getPortName(), nodes.get(dst).index));
        nodes.get(dst).prev.add(new Pair(dstPort.getPortName(), nodes.get(src).index));
    }

}

class Pair{
    public String Port;
    public int Index;
    public Pair(String Port, int Index){
        this.Index = Index;
        this.Port = Port;
    }

    @Override
    public String toString() {
        return Port + '-' + Index;
    }
}